# General imports
from utils_for_pygame import *
import Vector2 as vec
from Vector2 import Vector
import math

# Game imports
from entity import GameEntity

"""
Character class

Represents an in-game character
"""
class Character(GameEntity):
    def __init__(self, game, name=None, player=None):
        GameEntity.__init__(self, game, name)
        # Technical attributes
        if player is not None:
            self.player = player
        self.pos = Vector(0.0,0.0)
        self.vel = Vector(0.0,0.0)
        self.accel = Vector(0.0,0.0)
        self.on_ground = False
        self.jumping = False
        self.jump_time = 0
        self.running_left = False
        self.running_right = False
        self.size = 16 # Note: This is a radius
        
        # Graphics attributes
        self.image = load_image('player.png', -1)
        
        # Gameplay attributes
        self.run_speed = 1.0
        self.turn_speed = 1.25
        self.stop_speed = 1.25
        self.jump_power = 4.0
        self.max_jump_time = 10
        self.max_velx = 6.0
        self.max_vely = 100.0#10.0
        self.bounce_factor = 1.0
        # ...
    
    def run_left(self):
        self.running_left = True
        self.running_right = False
    def run_right(self):
        self.running_right = True
        self.running_left = False
    def stop_left(self):
        self.running_left = False
    def stop_right(self):
        self.running_right = False
    def jump_start(self):
        if not self.jumping and self.on_ground:
            self.jumping = True
            self.jump_time = self.game.get_ticks()
    def jump_end(self):
        if self.jumping:
            self.jumping = False
    
    def bounce(self):
        self.vel.y *= -self.bounce_factor
    
    def collided_with(self, other):
        tot_size = self.size + other.size
        return math.fabs(self.pos.x-other.pos.x) < tot_size and \
            math.fabs(self.pos.y-other.pos.y) < tot_size
    def hit_head(self, other):
        # Defines the "hit region" around the head
        return other.pos.y - self.pos.y > self.size + 0.2*other.size
    def moving_down(self):
        return self.vel.y > 0.0
    def moving_up(self):
        return self.vel.y < 0.0
    def moving_left(self):
        return self.vel.x < 0.0
    def moving_right(self):
        return self.vel.x > 0.0
    def squish(self, other):
        if self.hit_head(other):
            if self.moving_down():
                # Force move out of collision
                self.pos.y = other.pos.y - (self.size + other.size)
                # Tell the game about the event
                self.game.on_squished(self, other)
            else:
                # TODO: Not sure what effect this has, but it was in the original. Try it out!
                #if other.moving_up():
                #    other.vel.y = 0.0
                pass
            return True
        else:
            return False
    def bounce_with(self, other):
        between = self.pos.x - other.pos.x
        dist = math.fabs(between)
        overlap = (self.size+other.size) - dist
        direction = between / dist
        # Force positions to resolve collision
        self.pos.x  += 0.5 * overlap * direction
        other.pos.x += 0.5 * overlap * -direction
        # Swap velocities to conserve momentum
        self.vel.x, other.vel.x = other.vel.x, self.vel.x
        # Force velocities to move objects apart
        self.vel.x = math.fabs(self.vel.x) * direction
        other.vel.x = math.fabs(other.vel.x) * -direction
        
        # Let the game know about this event
        self.game.on_collided(self, other)
    
    # Interaction matrix
    def interact_with(self, other):
        if isinstance(other, Character):
            if self.collided_with(other):
                if not self.squish(other) and not other.squish(self):
                    self.bounce_with(other)
        else:
            return False
        return True
    
    def update(self):
        # Simple gravity
        g = 1.0
        self.accel += Vector(0.0, g)
        
        # TODO: This should be obtained from the current ground surface
        friction = 1.0
        
        # Apply running motion
        # TODO: Try to reduce the number of ifs in here
        if self.running_right:
            if self.vel.x < 0.0:
                self.vel.x += self.turn_speed * friction
            else:
                self.vel.x += self.run_speed * friction
        elif self.running_left:
            if self.vel.x > 0.0:
                self.vel.x -= self.turn_speed * friction
            else:
                self.vel.x -= self.run_speed * friction
        else:
            if self.on_ground:
                if self.vel.x < 0.0:
                    self.vel.x += self.stop_speed * friction
                    if self.vel.x > 0.0:
                        self.vel.x = 0.0
                else:
                    self.vel.x -= self.stop_speed * friction
                    if self.vel.x < 0.0:
                        self.vel.x = 0.0
        
        # Jumping
        # TODO: This probably needs to be re-thought-out
        if self.jumping:
            jump_duration = self.game.get_ticks() - self.jump_time
            jump_duration *= self.game.timestep
            #self.vel += Vector(0.0, -1.0) * self.jump_power
            f = (self.max_jump_time - jump_duration)/float(self.max_jump_time)
            self.accel += Vector(0.0, -1.0) * self.jump_power * f
            if jump_duration > self.max_jump_time:
                self.jumping = False
        
        # Integrate the motion
        # TODO: Implement a better integrator
        self.vel += self.accel * self.game.timestep
        self.pos += self.vel * self.game.timestep
        self.accel = Vector(0.0,0.0)
        
        # Ground plane
        if self.pos.y > 300:
            self.pos.y = 300
            self.vel.y = 0
            if not self.on_ground:
                self.player.on_touch_ground()
                self.on_ground = True
        else:
            if self.on_ground:
                self.player.on_leave_ground()
                self.on_ground = False
        
        # Velocity limits
        if self.vel.x*self.vel.x > self.max_velx*self.max_velx:
            self.vel.x *= self.max_velx / math.fabs(self.vel.x)
        if self.vel.y*self.vel.y > self.max_vely*self.max_vely:
            self.vel.y *= self.max_vely / math.fabs(self.vel.y)
    
    def draw(self, screen):
        rect = self.image.get_rect()
        screen.blit(self.image, (self.pos[0]-rect.w*0.5,\
                                 self.pos[1]-rect.h*0.5))
                        #special_flags=BLEND_ADD)
        """ # Draw a debugging rectangle
        rect = Rect(self.pos[0]-rect.w/2,\
                        self.pos[1]-rect.h/2,\
                        rect.w, rect.h)
        pygame.draw.rect(screen, (255,255,0), rect, 1)
        """
